// Version 1.0a
// Copyright (C) 1998, James R. Weeks and BioElectroMech.
// Visit BioElectroMech at www.obrador.com. Email James@obrador.com.

// See license.txt for details about the allowed used of this software.
// This software is based in part on the work of the Independent JPEG Group.
// See IJGreadme.txt for details about the Independent JPEG Group's license.

// This encoder is inspired by the Java Jpeg encoder by Florian Raemy,
// studwww.eurecom.fr/~raemy.
// It borrows a great deal of code and structure from the Independent
// Jpeg Group's Jpeg 6a library, Copyright Thomas G. Lane.
// See license.txt for details.

package james;

/**
 * DCT - A Java implementation of the Discreet Cosine Transform
 */
@SuppressWarnings("unused")
class DCT {
    /**
     * DCT Block Size - default 8
     */
    public int N = 8;

    /**
     * Image Quality (0-100) - default 80 (good image / good compression)
     */
    public int QUALITY = 80;

    public Object quantum[] = new Object[2];

    public Object Divisors[] = new Object[2];

    /**
     * Quantitization Matrix for luminace.
     */
    public int quantum_luminance[] = new int[this.N * this.N];

    public double DivisorsLuminance[] = new double[this.N * this.N];

    /**
     * Quantitization Matrix for chrominance.
     */
    public int quantum_chrominance[] = new int[this.N * this.N];

    public double DivisorsChrominance[] = new double[this.N * this.N];

    /**
     * Constructs a new DCT object. Initializes the cosine transform matrix
     * these are used when computing the DCT and it's inverse. This also
     * initializes the run length counters and the ZigZag sequence. Note that
     * the image quality can be worse than 25 however the image will be extemely
     * pixelated, usually to a block size of N.
     * 
     * @param QUALITY The quality of the image (0 worst - 100 best)
     */
    public DCT(final int QUALITY) {
        initMatrix(QUALITY);
    }

    /*
     * This method preforms a DCT on a block of image data using the AAN method
     * as implemented in the IJG Jpeg-6a library.
     */
    public double[][] forwardDCT(final float input[][]) {
        final double output[][] = new double[this.N][this.N];
        double tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
        double tmp10, tmp11, tmp12, tmp13;
        double z1, z2, z3, z4, z5, z11, z13;
        int i;
        int j;

        // Subtracts 128 from the input values
        for (i = 0; i < 8; i++) {
            for (j = 0; j < 8; j++) {
                output[i][j] = input[i][j] - 128.0;
                // input[i][j] -= 128;

            }
        }

        for (i = 0; i < 8; i++) {
            tmp0 = output[i][0] + output[i][7];
            tmp7 = output[i][0] - output[i][7];
            tmp1 = output[i][1] + output[i][6];
            tmp6 = output[i][1] - output[i][6];
            tmp2 = output[i][2] + output[i][5];
            tmp5 = output[i][2] - output[i][5];
            tmp3 = output[i][3] + output[i][4];
            tmp4 = output[i][3] - output[i][4];

            tmp10 = tmp0 + tmp3;
            tmp13 = tmp0 - tmp3;
            tmp11 = tmp1 + tmp2;
            tmp12 = tmp1 - tmp2;

            output[i][0] = tmp10 + tmp11;
            output[i][4] = tmp10 - tmp11;

            z1 = (tmp12 + tmp13) * 0.707106781;
            output[i][2] = tmp13 + z1;
            output[i][6] = tmp13 - z1;

            tmp10 = tmp4 + tmp5;
            tmp11 = tmp5 + tmp6;
            tmp12 = tmp6 + tmp7;

            z5 = (tmp10 - tmp12) * 0.382683433;
            z2 = 0.541196100 * tmp10 + z5;
            z4 = 1.306562965 * tmp12 + z5;
            z3 = tmp11 * 0.707106781;

            z11 = tmp7 + z3;
            z13 = tmp7 - z3;

            output[i][5] = z13 + z2;
            output[i][3] = z13 - z2;
            output[i][1] = z11 + z4;
            output[i][7] = z11 - z4;
        }

        for (i = 0; i < 8; i++) {
            tmp0 = output[0][i] + output[7][i];
            tmp7 = output[0][i] - output[7][i];
            tmp1 = output[1][i] + output[6][i];
            tmp6 = output[1][i] - output[6][i];
            tmp2 = output[2][i] + output[5][i];
            tmp5 = output[2][i] - output[5][i];
            tmp3 = output[3][i] + output[4][i];
            tmp4 = output[3][i] - output[4][i];

            tmp10 = tmp0 + tmp3;
            tmp13 = tmp0 - tmp3;
            tmp11 = tmp1 + tmp2;
            tmp12 = tmp1 - tmp2;

            output[0][i] = tmp10 + tmp11;
            output[4][i] = tmp10 - tmp11;

            z1 = (tmp12 + tmp13) * 0.707106781;
            output[2][i] = tmp13 + z1;
            output[6][i] = tmp13 - z1;

            tmp10 = tmp4 + tmp5;
            tmp11 = tmp5 + tmp6;
            tmp12 = tmp6 + tmp7;

            z5 = (tmp10 - tmp12) * 0.382683433;
            z2 = 0.541196100 * tmp10 + z5;
            z4 = 1.306562965 * tmp12 + z5;
            z3 = tmp11 * 0.707106781;

            z11 = tmp7 + z3;
            z13 = tmp7 - z3;

            output[5][i] = z13 + z2;
            output[3][i] = z13 - z2;
            output[1][i] = z11 + z4;
            output[7][i] = z11 - z4;
        }

        return output;
    }

    /*
     * This method preforms forward DCT on a block of image data using the
     * literal method specified for a 2-D Discrete Cosine Transform. It is
     * included as a curiosity and can give you an idea of the difference in the
     * compression result (the resulting image quality) by comparing its output
     * to the output of the AAN method below. It is ridiculously inefficient.
     */

    // For now the final output is unusable. The associated quantization step
    // needs some tweaking. If you get this part working, please let me know.

    public double[][] forwardDCTExtreme(final float input[][]) {
        final double output[][] = new double[this.N][this.N];
        final double tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
        final double tmp10, tmp11, tmp12, tmp13;
        final double z1, z2, z3, z4, z5, z11, z13;
        final int i;
        final int j;
        int v, u, x, y;
        for (v = 0; v < 8; v++) {
            for (u = 0; u < 8; u++) {
                for (x = 0; x < 8; x++) {
                    for (y = 0; y < 8; y++) {
                        output[v][u] += input[x][y] * Math.cos((double) (2 * x + 1) * (double) u * Math.PI / 16)
                                * Math.cos((double) (2 * y + 1) * (double) v * Math.PI / 16);
                    }
                }
                output[v][u] *= 0.25 * (u == 0 ? 1.0 / Math.sqrt(2) : (double) 1.0)
                        * (v == 0 ? 1.0 / Math.sqrt(2) : (double) 1.0);
            }
        }
        return output;
    }

    /*
     * This method sets up the quantization matrix for luminance and chrominance
     * using the Quality parameter.
     */
    private void initMatrix(final int quality) {
        final double[] AANscaleFactor = {
                1.0, 1.387039845, 1.306562965, 1.175875602, 1.0, 0.785694958, 0.541196100, 0.275899379 };
        int i;
        int j;
        int index;
        int Quality;
        int temp;

        // converting quality setting to that specified in the
        // jpeg_quality_scaling
        // method in the IJG Jpeg-6a C libraries

        Quality = quality;
        if (Quality <= 0) {
            Quality = 1;
        }
        if (Quality > 100) {
            Quality = 100;
        }
        if (Quality < 50) {
            Quality = 5000 / Quality;
        } else {
            Quality = 200 - Quality * 2;
        }

        // Creating the luminance matrix

        this.quantum_luminance[0] = 16;
        this.quantum_luminance[1] = 11;
        this.quantum_luminance[2] = 10;
        this.quantum_luminance[3] = 16;
        this.quantum_luminance[4] = 24;
        this.quantum_luminance[5] = 40;
        this.quantum_luminance[6] = 51;
        this.quantum_luminance[7] = 61;
        this.quantum_luminance[8] = 12;
        this.quantum_luminance[9] = 12;
        this.quantum_luminance[10] = 14;
        this.quantum_luminance[11] = 19;
        this.quantum_luminance[12] = 26;
        this.quantum_luminance[13] = 58;
        this.quantum_luminance[14] = 60;
        this.quantum_luminance[15] = 55;
        this.quantum_luminance[16] = 14;
        this.quantum_luminance[17] = 13;
        this.quantum_luminance[18] = 16;
        this.quantum_luminance[19] = 24;
        this.quantum_luminance[20] = 40;
        this.quantum_luminance[21] = 57;
        this.quantum_luminance[22] = 69;
        this.quantum_luminance[23] = 56;
        this.quantum_luminance[24] = 14;
        this.quantum_luminance[25] = 17;
        this.quantum_luminance[26] = 22;
        this.quantum_luminance[27] = 29;
        this.quantum_luminance[28] = 51;
        this.quantum_luminance[29] = 87;
        this.quantum_luminance[30] = 80;
        this.quantum_luminance[31] = 62;
        this.quantum_luminance[32] = 18;
        this.quantum_luminance[33] = 22;
        this.quantum_luminance[34] = 37;
        this.quantum_luminance[35] = 56;
        this.quantum_luminance[36] = 68;
        this.quantum_luminance[37] = 109;
        this.quantum_luminance[38] = 103;
        this.quantum_luminance[39] = 77;
        this.quantum_luminance[40] = 24;
        this.quantum_luminance[41] = 35;
        this.quantum_luminance[42] = 55;
        this.quantum_luminance[43] = 64;
        this.quantum_luminance[44] = 81;
        this.quantum_luminance[45] = 104;
        this.quantum_luminance[46] = 113;
        this.quantum_luminance[47] = 92;
        this.quantum_luminance[48] = 49;
        this.quantum_luminance[49] = 64;
        this.quantum_luminance[50] = 78;
        this.quantum_luminance[51] = 87;
        this.quantum_luminance[52] = 103;
        this.quantum_luminance[53] = 121;
        this.quantum_luminance[54] = 120;
        this.quantum_luminance[55] = 101;
        this.quantum_luminance[56] = 72;
        this.quantum_luminance[57] = 92;
        this.quantum_luminance[58] = 95;
        this.quantum_luminance[59] = 98;
        this.quantum_luminance[60] = 112;
        this.quantum_luminance[61] = 100;
        this.quantum_luminance[62] = 103;
        this.quantum_luminance[63] = 99;

        for (j = 0; j < 64; j++) {
            temp = (this.quantum_luminance[j] * Quality + 50) / 100;
            if (temp <= 0) {
                temp = 1;
            }
            if (temp > 255) {
                temp = 255;
            }
            this.quantum_luminance[j] = temp;
        }
        index = 0;
        for (i = 0; i < 8; i++) {
            for (j = 0; j < 8; j++) {
                // The divisors for the LL&M method (the slow integer method
                // used in
                // jpeg 6a library). This method is currently (04/04/98)
                // incompletely
                // implemented.
                // DivisorsLuminance[index] = ((double)
                // quantum_luminance[index]) << 3;
                // The divisors for the AAN method (the float method used in
                // jpeg 6a library.
                this.DivisorsLuminance[index] = 1.0 / (this.quantum_luminance[index] * AANscaleFactor[i]
                        * AANscaleFactor[j] * 8.0);
                index++;
            }
        }

        // Creating the chrominance matrix

        this.quantum_chrominance[0] = 17;
        this.quantum_chrominance[1] = 18;
        this.quantum_chrominance[2] = 24;
        this.quantum_chrominance[3] = 47;
        this.quantum_chrominance[4] = 99;
        this.quantum_chrominance[5] = 99;
        this.quantum_chrominance[6] = 99;
        this.quantum_chrominance[7] = 99;
        this.quantum_chrominance[8] = 18;
        this.quantum_chrominance[9] = 21;
        this.quantum_chrominance[10] = 26;
        this.quantum_chrominance[11] = 66;
        this.quantum_chrominance[12] = 99;
        this.quantum_chrominance[13] = 99;
        this.quantum_chrominance[14] = 99;
        this.quantum_chrominance[15] = 99;
        this.quantum_chrominance[16] = 24;
        this.quantum_chrominance[17] = 26;
        this.quantum_chrominance[18] = 56;
        this.quantum_chrominance[19] = 99;
        this.quantum_chrominance[20] = 99;
        this.quantum_chrominance[21] = 99;
        this.quantum_chrominance[22] = 99;
        this.quantum_chrominance[23] = 99;
        this.quantum_chrominance[24] = 47;
        this.quantum_chrominance[25] = 66;
        this.quantum_chrominance[26] = 99;
        this.quantum_chrominance[27] = 99;
        this.quantum_chrominance[28] = 99;
        this.quantum_chrominance[29] = 99;
        this.quantum_chrominance[30] = 99;
        this.quantum_chrominance[31] = 99;
        this.quantum_chrominance[32] = 99;
        this.quantum_chrominance[33] = 99;
        this.quantum_chrominance[34] = 99;
        this.quantum_chrominance[35] = 99;
        this.quantum_chrominance[36] = 99;
        this.quantum_chrominance[37] = 99;
        this.quantum_chrominance[38] = 99;
        this.quantum_chrominance[39] = 99;
        this.quantum_chrominance[40] = 99;
        this.quantum_chrominance[41] = 99;
        this.quantum_chrominance[42] = 99;
        this.quantum_chrominance[43] = 99;
        this.quantum_chrominance[44] = 99;
        this.quantum_chrominance[45] = 99;
        this.quantum_chrominance[46] = 99;
        this.quantum_chrominance[47] = 99;
        this.quantum_chrominance[48] = 99;
        this.quantum_chrominance[49] = 99;
        this.quantum_chrominance[50] = 99;
        this.quantum_chrominance[51] = 99;
        this.quantum_chrominance[52] = 99;
        this.quantum_chrominance[53] = 99;
        this.quantum_chrominance[54] = 99;
        this.quantum_chrominance[55] = 99;
        this.quantum_chrominance[56] = 99;
        this.quantum_chrominance[57] = 99;
        this.quantum_chrominance[58] = 99;
        this.quantum_chrominance[59] = 99;
        this.quantum_chrominance[60] = 99;
        this.quantum_chrominance[61] = 99;
        this.quantum_chrominance[62] = 99;
        this.quantum_chrominance[63] = 99;

        for (j = 0; j < 64; j++) {
            temp = (this.quantum_chrominance[j] * Quality + 50) / 100;
            if (temp <= 0) {
                temp = 1;
            }
            if (temp >= 255) {
                temp = 255;
            }
            this.quantum_chrominance[j] = temp;
        }
        index = 0;
        for (i = 0; i < 8; i++) {
            for (j = 0; j < 8; j++) {
                // The divisors for the LL&M method (the slow integer method
                // used in
                // jpeg 6a library). This method is currently (04/04/98)
                // incompletely
                // implemented.
                // DivisorsChrominance[index] = ((double)
                // quantum_chrominance[index]) << 3;
                // The divisors for the AAN method (the float method used in
                // jpeg 6a library.
                this.DivisorsChrominance[index] = 1.0 / (this.quantum_chrominance[index] * AANscaleFactor[i]
                        * AANscaleFactor[j] * 8.0);
                index++;
            }
        }

        // quantum and Divisors are objects used to hold the appropriate matices

        this.quantum[0] = this.quantum_luminance;
        this.Divisors[0] = this.DivisorsLuminance;
        this.quantum[1] = this.quantum_chrominance;
        this.Divisors[1] = this.DivisorsChrominance;

    }

    /*
     * This method quantitizes data and rounds it to the nearest integer.
     */
    public int[] quantizeBlock(final double inputData[][], final int code) {
        final int outputData[] = new int[this.N * this.N];
        int i, j;
        int index;
        index = 0;
        for (i = 0; i < 8; i++) {
            for (j = 0; j < 8; j++) {
                // The second line results in significantly better compression.
                outputData[index] = (int) Math.round(inputData[i][j] * ((double[]) this.Divisors[code])[index]);
                // outputData[index] = (int)(((inputData[i][j] * (((double[])
                // (Divisors[code]))[index])) + 16384.5) -16384);
                index++;
            }
        }

        return outputData;
    }

    /*
     * This is the method for quantizing a block DCT'ed with forwardDCTExtreme
     * This method quantitizes data and rounds it to the nearest integer.
     */
    public int[] quantizeBlockExtreme(final double inputData[][], final int code) {
        final int outputData[] = new int[this.N * this.N];
        int i, j;
        int index;
        index = 0;
        for (i = 0; i < 8; i++) {
            for (j = 0; j < 8; j++) {
                outputData[index] = (int) Math.round(inputData[i][j] / ((int[]) this.quantum[code])[index]);
                index++;
            }
        }

        return outputData;
    }
}
